import cv2
import numpy as np
from flask import Flask, jsonify, Response, send_file
from flask_socketio import SocketIO
import threading
import time
import uuid
import os
from io import BytesIO
import logging

# 全局变量
app = Flask(__name__)
socketio = SocketIO(app, cors_allowed_origins="*")

# 存储每个视频流的状态：{ stream_id: { 'cap': cv2.VideoCapture, 'frame_queue': deque, 'running': bool, 'url': str } }
video_streams = {}

# 线程锁，防止并发修改
stream_lock = threading.Lock()

# ===== 健康检查配置 =====
HEALTH_CHECK_INTERVAL = 5        # 每 5 秒检查一次
STREAM_TIMEOUT = 10              # 超过 10 秒无更新视为失效
AUTO_RECOVER = True              # 是否尝试重连失败的流？这里我们选择清理

def health_check_worker():
    """
    健康检查后台线程：定期清理无更新的视频流
    """
    while True:
        time.sleep(HEALTH_CHECK_INTERVAL)
        current_time = time.time()
        streams_to_remove = []

        with stream_lock:
            for stream_id, info in video_streams.items():
                last_update = info.get('last_update')
                if last_update is None:
                    # 还没拿到第一帧，等一等
                    continue
                if (current_time - last_update) > STREAM_TIMEOUT:
                    print(f"🧹 健康检查: 视频流 {stream_id} 超时无更新，自动清理")
                    streams_to_remove.append(stream_id)

            # 在锁外执行清理（避免在遍历时修改字典）
            for stream_id in streams_to_remove:
                info = video_streams[stream_id]
                info['stop_event'].set()
                del video_streams[stream_id]

        # 可选：触发事件或日志
        if streams_to_remove:
            print(f"🗑️ 已清理 {len(streams_to_remove)} 个失效流: {streams_to_remove}")


def get_latest_frame_from_camera(url, stream_id, stop_event):
    """
    独立线程运行：打开视频流，持续读取最新帧并存入队列
    """
    cap = cv2.VideoCapture(url)
    if not cap.isOpened():
        print(f"❌ 无法打开视频流: {url}")
        with stream_lock:
            if stream_id in video_streams:
                video_streams[stream_id]['error'] = f"无法打开: {url}"
        return

    print(f"✅ 开始读取视频流: {url}")

    while not stop_event.is_set():
        ret, frame = cap.read()
        if not ret:
            print(f"⚠️ 视频流中断，尝试重连: {url}")
            cap.release()
            time.sleep(1)
            cap = cv2.VideoCapture(url)
            continue

        # 只保留最新一帧（缩小内存占用）
        _, buffer = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, 80])
        jpg_data = buffer.tobytes()

        with stream_lock:
            if stream_id in video_streams:
                video_streams[stream_id]['latest_frame'] = jpg_data
                video_streams[stream_id]['last_update'] = time.time()

        time.sleep(0.01)  # 防止占用过高 CPU

    cap.release()
    print(f"⏹️ 视频流已停止: {stream_id}")

@app.route('/add_stream', methods=['POST'])
def add_stream():
    """
    添加视频流
    请求体 JSON: { "url": "rtsp://...", "stream_id": "optional_id" }
    """
    import json
    from flask import request

    data = request.get_json() or {}
    url = data.get('url')
    stream_id = data.get('stream_id')

    if not url:
        return jsonify({"error": "缺少 'url' 参数"}), 400

    if not stream_id:
        stream_id = str(uuid.uuid4())  # 自动生成唯一 ID

    with stream_lock:
        if stream_id in video_streams:
            return jsonify({"error": "stream_id 已存在", "stream_id": stream_id}), 400

    # 创建停止事件（用于控制线程）
    stop_event = threading.Event()

    # 保存流信息
    with stream_lock:
        video_streams[stream_id] = {
            'url': url,
            'stop_event': stop_event,
            'latest_frame': None,
            'last_update': None,
            'running': True
        }

    # 启动解码线程
    thread = threading.Thread(
        target=get_latest_frame_from_camera,
        args=(url, stream_id, stop_event),
        daemon=True
    )
    thread.start()

    return jsonify({
        "message": "视频流已添加",
        "stream_id": stream_id,
        "url": url
    }), 201


@app.route('/remove_stream', methods=['POST'])
def remove_stream():
    """
    删除视频流
    请求体 JSON: { "stream_id": "..." }
    """
    import json
    from flask import request

    data = request.get_json() or {}
    stream_id = data.get('stream_id')

    if not stream_id:
        return jsonify({"error": "缺少 'stream_id' 参数"}), 400

    with stream_lock:
        if stream_id not in video_streams:
            return jsonify({"error": "stream_id 不存在"}), 404

        # 设置停止事件
        video_streams[stream_id]['stop_event'].set()
        del video_streams[stream_id]
    
    return jsonify({"message": "视频流已删除", "stream_id": stream_id})


@app.route('/frame/<stream_id>')
def get_latest_frame(stream_id):
    """
    获取指定视频流的最新帧（返回 JPEG 图片）
    """
    with stream_lock:
        if stream_id not in video_streams:
            return jsonify({"error": "stream_id 不存在"}), 404

        frame_data = video_streams[stream_id].get('latest_frame')
        if not frame_data:
            return jsonify({"error": "暂无帧数据"}), 404

    # 返回图片
    return Response(
        frame_data,
        mimetype='image/jpeg'
    )


@app.route('/list_streams')
def list_streams():
    """
    列出所有活动视频流
    """
    with stream_lock:
        info = {}
        for sid, info_dict in video_streams.items():
            info[sid] = {
                'url': info_dict['url'],
                'running': info_dict['running'],
                'last_update': info_dict.get('last_update'),
                'has_frame': info_dict['latest_frame'] is not None
            }
    return jsonify(info)


# ======================
# 可选：通过 SocketIO 实时推送帧（用于 Web 预览）
# ======================
@socketio.on('start_stream')
def handle_start_stream(data):
    stream_id = data.get('stream_id')
    if stream_id in video_streams:
        frame_data = video_streams[stream_id].get('latest_frame')
        if frame_data:
            socketio.emit('video_frame', {'stream_id': stream_id, 'frame': frame_data})


# ======================
# 主程序入口
# ======================
if __name__ == '__main__':
    print("🚀 视频流管理服务启动中...")
    print("接口:")
    print("  POST   /add_stream     - 添加视频流")
    print("  POST   /remove_stream  - 删除视频流")
    print("  GET    /frame/<id>     - 获取最新帧")
    print("  GET    /list_streams   - 查看所有流")
    
    
    # 🔁 启动健康检查线程
    health_thread = threading.Thread(target=health_check_worker, daemon=True)
    health_thread.start()

    socketio.run(app, host='0.0.0.0', port=5000, debug=False, use_reloader=False)